﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;

namespace SilverlightDemo
{
    public class DragScrollViewerAdaptor
    {
        private ScrollViewer _wrappedScrollViewer;
        public DragScrollViewerAdaptor(ScrollViewer scrollViewerToWrap)
        {
            _wrappedScrollViewer = scrollViewerToWrap;

            _wrappedScrollViewer.MouseLeave += WrappedScrollViewer_MouseLeave;
            _wrappedScrollViewer.MouseLeftButtonDown += WrappedScrollViewer_MouseLeftButtonDown;

            WrapScrollViewerContents();

            _wrappedScrollViewer.MouseMove += WrappedScrollViewer_MouseMove;
            _wrappedScrollViewer.MouseLeftButtonUp += WrappedScrollViewer_MouseLeftButtonUp;
        }

        private void WrapScrollViewerContents()
        {
            var content = _wrappedScrollViewer.Content as UIElement;
            if (content != null)
                content.MouseLeftButtonDown += WrappedScrollViewer_MouseLeftButtonDown;
        }

        private void WrappedScrollViewer_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            CancelDrag(PreviousVelocity);
        }

        private void WrappedScrollViewer_MouseMove(object sender, MouseEventArgs e)
        {
            _currentPoint = e.GetPosition(_wrappedScrollViewer);
            if (_mouseDown && !_isDragging)
            {
                _isDragging = true;
                DragScroll();
            }
        }

        private void WrappedScrollViewer_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            _currentPoint = _previousPoint = _previousPreviousPoint = e.GetPosition(_wrappedScrollViewer);
            Momentum = new Vector(0, 0);
            BeginDrag();
        }

        private void WrappedScrollViewer_MouseLeave(object sender, MouseEventArgs e)
        {
            CancelDrag(PreviousVelocity);
        }



        
        
        private const double DEFAULT_FRICTION = 0.2;
        private const double MINIMUM_FRICTION = 0.0;
        private const double MAXIMUM_FRICTION = 1.0;

        private double _friction = DEFAULT_FRICTION;
        public double Friction
        {
            get
            {
                return _friction;
            }
            set
            {
                _friction = Math.Min(Math.Max(value, MINIMUM_FRICTION), MAXIMUM_FRICTION);
            }
        }




        private Point _previousPreviousPoint;
        private Point _previousPoint;
        private Point _currentPoint;


        private bool _mouseDown = false;
        private bool _isDragging = false;

        private void BeginDrag()
        {
            _mouseDown = true;
            _wrappedScrollViewer.Cursor = Cursors.Hand;
        }

        private void CancelDrag(Vector velocityToUse)
        {
            if (_isDragging)
                Momentum = velocityToUse;
            _isDragging = false;
            _mouseDown = false;
            _wrappedScrollViewer.Cursor = Cursors.Arrow;
        }


        protected void DragScroll()
        {
            if (_dragScrollTimer == null)
            {
                _dragScrollTimer = new DispatcherTimer();
                _dragScrollTimer.Tick += TickDragScroll;
                _dragScrollTimer.Interval = new TimeSpan(0, 0, 0, 0, (int)DRAG_POLLING_INTERVAL);
                _dragScrollTimer.Start();
            }
        }

        private void TickDragScroll(object sender, EventArgs e)
        {
            if (_isDragging)
            {

                GeneralTransform generalTransform = _wrappedScrollViewer.TransformToVisual(_wrappedScrollViewer);
                Point childToParentCoordinates = generalTransform.Transform(new Point(0, 0));
                Rect bounds = new Rect(childToParentCoordinates, _wrappedScrollViewer.RenderSize);

                if (bounds.Contains(_currentPoint))
                {
                    PerformScroll(PreviousVelocity);
                }

                if (!_mouseDown)
                {
                    CancelDrag(Velocity);
                }
                _previousPreviousPoint = _previousPoint;
                _previousPoint = _currentPoint;
            }
            else if (Momentum.Length > 0)
            {
                Momentum *= (1.0 - _friction / 4.0);
                PerformScroll(Momentum);
            }
            else
            {
                if (_dragScrollTimer != null)
                {
                    _dragScrollTimer.Tick -= TickDragScroll;
                    _dragScrollTimer.Stop();
                    _dragScrollTimer = null;
                }
            }
        }

        private void CancelDrag()
        {
            _isDragging = false;
            Momentum = Velocity;
        }

        private void PerformScroll(Vector displacement)
        {
            var verticalOffset = Math.Max(0.0, _wrappedScrollViewer.VerticalOffset - displacement.Y);
            _wrappedScrollViewer.ScrollToVerticalOffset(verticalOffset);

            var horizontalOffset = Math.Max(0.0, _wrappedScrollViewer.HorizontalOffset - displacement.X);
            _wrappedScrollViewer.ScrollToHorizontalOffset(horizontalOffset);
        }

        private const double DRAG_POLLING_INTERVAL = 10; // milliseconds
        private DispatcherTimer _dragScrollTimer = null;


        private Vector Momentum { get; set; }
        private Vector Velocity
        {
            get
            {
                return new Vector(_currentPoint.X - _previousPoint.X, _currentPoint.Y - _previousPoint.Y);
            }
        }
        // Using PreviousVelocity gives a smoother, better feeling as it leaves out any last frame momentum changes
        private Vector PreviousVelocity
        {
            get
            {
                return new Vector(_previousPoint.X - _previousPreviousPoint.X, _previousPoint.Y - _previousPreviousPoint.Y);
            }
        }

        private class Vector
        {
            public double Length { get { return Math.Sqrt(X * X + Y * Y); } }
            public Vector(double x, double y)
            {
                X = x;
                Y = y;
            }

            public static Vector operator *(Vector vector, double scalar)
            {
                return new Vector(vector.X * scalar, vector.Y * scalar);
            }

            public double X { get; set; }
            public double Y { get; set; }

        }
    }
}